﻿// Copyright (c) 2009, Heiko Wundram (modelnine@h8me.eu).
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
// conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
//   in the documentation and/or other materials provided with the distribution.
// * Neither the name of Heiko Wundram (h8me.eu) nor the names of its contributors may be used to endorse or promote products
//   derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
// BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

using System;
using System.Collections.Generic;
using System.Globalization;
using eu.h8me.Parsing.LexerLib;
//using eu.h8me.Parsing.LexerLib2;
using eu.h8me.Parsing.ParserLib;

namespace eu.h8me.Parsing.Calculator {

    /// <summary>
    /// Simple calculator implemented using the LR-parsing library from eu.h8me.Parsing and demonstrating the library usage. Due
    /// to precedence declarations on tokens, the grammar is not ambiguous.
    /// </summary>
    public static class Program {

        // Data.
        private static RegExSet _RegExSet = new RegExSet();
        private static Grammar _Grammar = new Grammar();

        /// <summary>
        /// Program entry point.
        /// </summary>
        /// <param name="args">Program arguments.</param>
        public static void Main(string[] args) {
            // Build lexer and parser.
            BuildLexer();
            BuildParser();
            eu.h8me.Parsing.LexerLib2.Demo.BuildTNFA();

            // Loop reading lines.
            CalculatorState cstate = new CalculatorState();
            while( true ) {
                // Fetch line and quit on empty line.
                Console.WriteLine("Please enter expression, or close by entering empty line");
                string line = Console.ReadLine();
                if( line.Length == 0 )
                    break;

                // Build fresh parser and lexer callback object from lexer object.
                Parser parser = _Grammar.Parser;
                Parser.LexerCallback lexer = _RegExSet.Lexer.Lex(line);

                // Initialize parser tag from current calculator state.
                parser.Tag = cstate;

                // Invoke the parser on the lexer callback. Return value is the reduced token.
                double rv = (double)parser.Parse(lexer);
                Console.WriteLine("The result is: {0}", rv);
            }
        }

        /// <summary>
        /// Build the lexer regular expression object.
        /// </summary>
        private static void BuildLexer() {
            // Lexer value tokens with a match event bound (which might return a token).
            _RegExSet.Add("[0-9]+(\\.[0-9]*)?(e[+\\-]?[0-9]+)?@c").MatchEvent += LexNumber;
            _RegExSet.Add("[+\\-*/^()=]").MatchEvent += RegEx.Token();

            // Identifier parsing (returns symbol "id" carrying data).
            _RegExSet.Add("[a-z_][0-9a-z_]*@c").MatchEvent += RegEx.Token("id");

            // Ignorable tokens. When no match event is bound, matching the regex never yields a token.
            _RegExSet.Add("[ \r\t\n]+");

            // Finalize regular expression.
            foreach( string warn in _RegExSet.Finish() )
                Console.Error.WriteLine("Warning from RegExSet: {0}", warn);
        }

        /// <summary>
        /// Process a number found in the input.
        /// </summary>
        /// <param name="state">Current lexer state.</param>
        /// <param name="data">String data associated with matched regular expression.</param>
        /// <returns>Number token.</returns>
        private static Token LexNumber(LexState state, string data) {
            // Return number token.
            return state.Grammar["number"] / Convert.ToDouble(data, CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// Build the grammar object to parse expressions.
        /// </summary>
        private static void BuildParser() {
            // Initialize symbol precedences for expression symbols.
            _Grammar["+"].SymbolPrecedence = _Grammar["-"].SymbolPrecedence = 1;
            _Grammar["*"].SymbolPrecedence = _Grammar["/"].SymbolPrecedence = 2;
            _Grammar["^"].SymbolPrecedence = 4;

            // Binary operator expressions.
            _Grammar["valexpr"].Add(_Grammar["valexpr"] + _Grammar["+"] + _Grammar["valexpr"]).ReduceEvent += BinaryOperator;
            _Grammar["valexpr"].Add(_Grammar["valexpr"] + _Grammar["-"] + _Grammar["valexpr"]).ReduceEvent += BinaryOperator;
            _Grammar["valexpr"].Add(_Grammar["valexpr"] + _Grammar["*"] + _Grammar["valexpr"]).ReduceEvent += BinaryOperator;
            _Grammar["valexpr"].Add(_Grammar["valexpr"] + _Grammar["/"] + _Grammar["valexpr"]).ReduceEvent += BinaryOperator;
            _Grammar["valexpr"].Add(_Grammar["valexpr"] + _Grammar["^"] + _Grammar["valexpr"]).ReduceEvent += BinaryOperator;

            // Unary plus operator expression with explicit rule precedence (not related to terminal symbol).
            Production uplus = _Grammar["valexpr"].Add(_Grammar["+"] + _Grammar["valexpr"]);
            uplus.RulePrecedence = 3;
            uplus.ReduceEvent += UnaryOperator;

            // Unary minus operator expression with explicit rule precedence (not related to terminal symbol).
            Production uminus = _Grammar["valexpr"].Add(_Grammar["-"] + _Grammar["valexpr"]);
            uminus.RulePrecedence = 3;
            uminus.ReduceEvent += UnaryOperator;

            // Basic expressions; use Fetch helper to return respective symbols data directly (default event is Fetch(0)).
            // The brackets allow an assignment expression to be contained within (value of an assignment is the assigned value).
            _Grammar["valexpr"].Add(_Grammar["number"]);
            _Grammar["valexpr"].Add(_Grammar["("] + _Grammar["expr"] + _Grammar[")"]).ReduceEvent += Production.Fetch(1);

            // Identifier matching (for lookup in dictionary or function call).
            _Grammar["valexpr"].Add(_Grammar["id"]).ReduceEvent += LookupName;

            // Assignment expression (for assigning variable names), along with the terminating rule for the right recursion.
            _Grammar["expr"].Add(_Grammar["id"] + _Grammar["="] + _Grammar["expr"]).ReduceEvent += SetName;
            _Grammar["expr"].Add(_Grammar["valexpr"]);

            // Finalize grammar.
            foreach( string warn in _Grammar.Finish("expr") )
                Console.Error.WriteLine("Warning from Grammar: {0}", warn);
        }

        /// <summary>
        /// Process a binary operator expression.
        /// </summary>
        /// <param name="state">Current parsing state.</param>
        /// <param name="symbols">Symbols in the current production.</param>
        /// <returns>Output value of invoking the binary operator.</returns>
        private static object BinaryOperator(ParseState state, Token[] symbols) {
            // Check operator type.
            switch( ( (string)symbols[1].Data )[0] ) {
                case '+':
                    // Add.
                    return (double)symbols[0].Data + (double)symbols[2].Data;
                case '-':
                    // Subtract.
                    return (double)symbols[0].Data - (double)symbols[2].Data;
                case '*':
                    // Multiply.
                    return (double)symbols[0].Data * (double)symbols[2].Data;
                case '/':
                    // Divide.
                    return (double)symbols[0].Data / (double)symbols[2].Data;
                case '^':
                    // Power.
                    return Math.Pow((double)symbols[0].Data, (double)symbols[2].Data);
                default:
                    // Can't get here.
                    throw new InvalidOperationException("Got invalid symbol on parse, grammar broken?");
            }
        }

        /// <summary>
        /// Process a unary operator expression.
        /// </summary>
        /// <param name="state">Current parsing state.</param>
        /// <param name="symbols">Symbols in the current production.</param>
        /// <returns>Output value of invoking the unary operator.</returns>
        private static object UnaryOperator(ParseState state, Token[] symbols) {
            // Check operator type.
            switch( ( (string)symbols[0].Data )[0] ) {
                case '+':
                    // Unary plus, just return value.
                    return symbols[1].Data;
                case '-':
                    // Unary minus, negate value.
                    return -(double)symbols[1].Data;
                default:
                    // Can't get here.
                    throw new InvalidOperationException("Got invalid symbol on parse, grammar broken?");
            }
        }

        /// <summary>
        /// Look up the passed name in the set of currently registered names, returning the value for it. In case the name
        /// isn't defined, we throw a parse exception.
        /// </summary>
        /// <param name="state">Current parsing state.</param>
        /// <param name="symbols">Name to look up.</param>
        /// <returns>Number value resolving to the stored name.</returns>
        private static object LookupName(ParseState state, Token[] symbols) {
            // Fetch name from state.
            CalculatorState cstate = (CalculatorState)state.Parser.Tag;
            double val;
            if( cstate.LookupVariable((string)symbols[0].Data, out val) )
                return val;

            // Name unknown, fail the reduction (which fails the parse, as there is no other parse path to take).
            throw new FailReductionException();
        }

        /// <summary>
        /// Set the passed name as a variable name in the contained calculator variable store. This defines/redefines the value
        /// that is associated with the name.
        /// </summary>
        /// <param name="state">Current parsing state.</param>
        /// <param name="symbols">Name to set and value for it.</param>
        /// <returns>The value that was assigned to the name.</returns>
        private static object SetName(ParseState state, Token[] symbols) {
            // Store value that was calculated to calculator state.
            CalculatorState cstate = (CalculatorState)state.Parser.Tag;
            cstate.SetVariable((string)symbols[0].Data, (double)symbols[2].Data);

            // Return value.
            return (double)symbols[2].Data;
        }

    }

    /// <summary>
    /// Helper class which stores calculator state (specifically the name,value dictionary and function refs).
    /// </summary>
    internal class CalculatorState {

        // Data.
        private Dictionary<string, double> _Variables = new Dictionary<string, double>() {
            { "pi", Math.PI },
            { "e", Math.E }
        };

        /// <summary>
        /// Initialize a new calculator state instance.
        /// </summary>
        public CalculatorState() {
            // No specific construction.
        }

        /// <summary>
        /// Look up the passed name in the current set of variables.
        /// </summary>
        /// <param name="name">Name to look up.</param>
        /// <param name="val">Value if found, 0.0 otherwise.</param>
        /// <returns>Value indicating whether the name was found.</returns>
        public bool LookupVariable(string name, out double val) {
            // Fetch name.
            return _Variables.TryGetValue(name, out val);
        }

        /// <summary>
        /// Store the passed value in the variable named name.
        /// </summary>
        /// <param name="name">The name to set up.</param>
        /// <param name="val">The value to store with the name.</param>
        public void SetVariable(string name, double val) {
            // Store the value.
            _Variables[name] = val;
        }

    }

}
